package com.siashan.toolkit.image;

import com.siashan.toolkit.image.element.AbstractElement;
import com.siashan.toolkit.image.element.ImageElement;
import com.siashan.toolkit.image.element.RectangleElement;
import com.siashan.toolkit.image.element.TextElement;
import com.siashan.toolkit.image.enums.OutputFormat;
import com.siashan.toolkit.image.enums.ZoomMode;
import com.siashan.toolkit.image.painter.IPainter;
import com.siashan.toolkit.image.painter.PainterFactory;
import com.siashan.toolkit.image.utils.ElementUtil;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * 图片合成
 *
 * @author siashan
 * @date 2021/12/24
 * @since 1.0.8
 */
public class ImageProcessor {
    /**
     * 待绘制的元素集合
     */
    private List<AbstractElement> combineElements = new ArrayList<>();
    /**
     *  合成后的图片对象
     */
    private BufferedImage combinedImage;
    /**
     *  画布宽度
     */
    private int canvasWidth;
    /**
     *  画布高度
     */
    private int canvasHeight;
    /**
     * 输出图片格式
     */
    private OutputFormat outputFormat;
    /**
     * 画布圆角（针对整图）
     */
    private Integer roundCorner;

    /***************** 构造函数 *******************/

    /**
     * 构造方法
     *
     * @apiNote 绘制无底图空白图片
     * @param canvasWidth  画布宽
     * @param canvasHeight 画布高
     * @param outputFormat 输出图片格式
     */
    public ImageProcessor(int canvasWidth, int canvasHeight, OutputFormat outputFormat) {
        this.canvasWidth = canvasWidth;
        this.canvasHeight = canvasHeight;
        this.outputFormat = outputFormat;
    }

    /**
     * 构造方法
     *
     * @param canvasWidth  画布宽
     * @param canvasHeight 画布高
     * @param bgColor      画布颜色（如果需要透明背景，不要设这个参数，比方图片边缘是圆角的场景）
     * @param outputFormat 输出图片格式
     */
    public ImageProcessor(int canvasWidth, int canvasHeight, Color bgColor, OutputFormat outputFormat) {
        this.canvasWidth = canvasWidth;
        this.canvasHeight = canvasHeight;
        this.outputFormat = outputFormat;
        RectangleElement bgElement = new RectangleElement(0, 0, canvasWidth, canvasHeight);
        bgElement.setColor(bgColor);
        this.combineElements.add(bgElement);
    }

    /**
     * 构造方法
     *
     * @param bgImageUrl   背景图片地址（画布以背景图宽高为基准）
     * @param outputFormat 输出图片格式
     * @throws Exception
     */
    public ImageProcessor(String bgImageUrl, OutputFormat outputFormat) throws Exception {
        this(ImageIO.read(new URL(bgImageUrl)), outputFormat);
    }

    /**
     * 构造方法
     *
     * @param bgImage      背景图片对象（画布以背景图宽高为基准）
     * @param outputFormat 输出图片格式
     * @throws Exception
     */
    public ImageProcessor(BufferedImage bgImage, OutputFormat outputFormat) {
        ImageElement bgImageElement = new ImageElement(bgImage, 0, 0);
        this.combineElements.add(bgImageElement);
        this.canvasWidth = bgImage.getWidth();
        this.canvasHeight = bgImage.getHeight();
        this.outputFormat = outputFormat;
    }

    /**
     * 构造方法
     *
     * @param bgStream      背景图片文件流（画布以背景图宽高为基准）
     * @param outputFormat 输出图片格式
     * @throws Exception
     */
    public ImageProcessor(InputStream bgStream, OutputFormat outputFormat) throws IOException {
        this(ImageIO.read(bgStream), outputFormat);
    }

    /**
     * 构造方法
     *
     * @param bgImageUrl   背景图片地址
     * @param width        背景图宽度
     * @param height       背景图高度
     * @param zoomMode     缩放模式
     * @param outputFormat 输出图片格式
     * @throws Exception
     */
    public ImageProcessor(String bgImageUrl, int width, int height, ZoomMode zoomMode, OutputFormat outputFormat) throws Exception {
        this(ImageIO.read(new URL(bgImageUrl)), width, height, zoomMode, outputFormat);
    }

    /**
     * 构造方法
     *
     * @param bgImage      背景图片对象
     * @param width        背景图宽度
     * @param height       背景图高度
     * @param zoomMode     缩放模式
     * @param outputFormat 输出图片格式
     */
    public ImageProcessor(BufferedImage bgImage, int width, int height, ZoomMode zoomMode, OutputFormat outputFormat) {
        ImageElement bgImageElement = new ImageElement(bgImage, 0, 0, width, height, zoomMode);

        //计算画布新宽高
        int canvasWidth = 0;
        int canvasHeight = 0;

        switch (zoomMode) {
            case Origin:
                canvasWidth = bgImage.getWidth();
                canvasHeight = bgImage.getHeight();
                break;
            case Width:
                canvasWidth = width;
                canvasHeight = bgImage.getHeight() * canvasWidth / bgImage.getWidth();
                break;
            case Height:
                canvasHeight = height;
                canvasWidth = bgImage.getWidth() * canvasHeight / bgImage.getHeight();
                break;
            case WidthHeight:
                canvasHeight = width;
                canvasWidth = height;
                break;
        }

        this.combineElements.add(bgImageElement);
        this.canvasWidth = canvasWidth;
        this.canvasHeight = canvasHeight;
        this.outputFormat = outputFormat;
    }


    /***************** 公共方法 *******************/

    /**
     * 合成图片，返回图片对象
     *
     * @return 图片对象
     * @throws Exception
     */
    public BufferedImage combine() throws Exception {
        combinedImage = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = combinedImage.createGraphics();

        //PNG要做透明度处理，否则背景图透明部分会变黑
        if (outputFormat == OutputFormat.PNG) {
            combinedImage = g.getDeviceConfiguration().createCompatibleImage(canvasWidth, canvasHeight, Transparency.TRANSLUCENT);
            g = combinedImage.createGraphics();
        }
        //抗锯齿
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setColor(Color.white);

        //循环绘制
        for (AbstractElement element : combineElements) {
            IPainter painter = PainterFactory.createInstance(element);
            painter.draw(g, element, canvasWidth);
        }
        g.dispose();

        //处理整图圆角
        if (roundCorner != null) {
            combinedImage = ElementUtil.makeRoundCorner(combinedImage, canvasWidth, canvasHeight, roundCorner);
        }

        return combinedImage;
    }

    /**
     * 设置背景高斯模糊（毛玻璃效果）
     *
     * @param blur    模糊
     */
    public void setBackgroundBlur(int blur) {
        ImageElement bgElement = (ImageElement) combineElements.get(0);
        bgElement.setBlur(blur);
    }

    /**
     * 设置画布圆角（针对整图）
     * @param roundCorner    圆角
     */
    public void setCanvasRoundCorner(Integer roundCorner) {
        this.roundCorner = roundCorner;
    }


    /***************** 对象输出相关方法 *******************/

    /**
     * 获取合成后的图片对象
     *
     * @return 图片对象
     */
    public BufferedImage getCombinedImage() {
        return combinedImage;
    }

    /**
     * 获取合成后的图片流
     *
     * @return 图片流
     * @throws Exception
     */
    public InputStream getCombinedImageStream() throws Exception {
        if (combinedImage != null) {
            try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
                ImageIO.write(combinedImage, outputFormat.getName(), os);
                return new ByteArrayInputStream(os.toByteArray());
            } catch (Exception e) {
                throw new Exception("执行图片合成失败，无法输出文件流");
            }
        } else {
            throw new Exception("尚未执行图片合成，无法输出文件流");
        }
    }

    /**
     * 保存合成后的图片
     *
     * @param filePath 完整保存路径，如 “d://123.jpg”
     * @throws IOException
     */
    public void save(String filePath) throws Exception {
        if (combinedImage != null) {
            ImageIO.write(combinedImage, outputFormat.getName(), new File(filePath));
        } else {
            throw new Exception("尚未执行图片合成，无法保存文件");
        }
    }


    /***************** 创建和添加元素的方法 *******************/

    /**
     * 添加元素（图片或文本）
     *
     * @param element 图片或文本元素
     */
    public void addElement(AbstractElement element) {
        this.combineElements.add(element);
    }

    /**
     * 添加图片元素
     *
     * @param imgUrl 图片url
     * @param x      x坐标
     * @param y      y坐标
     * @return 图片元素
     */
    public ImageElement addImageElement(String imgUrl, int x, int y) throws Exception {
        ImageElement imageElement = new ImageElement(imgUrl, x, y);
        this.combineElements.add(imageElement);
        return imageElement;
    }

    /**
     * 添加图片元素
     *
     * @param image 图片对象
     * @param x     x坐标
     * @param y     y坐标
     * @return 图片元素
     */
    public ImageElement addImageElement(BufferedImage image, int x, int y) {
        ImageElement imageElement = new ImageElement(image, x, y);
        this.combineElements.add(imageElement);
        return imageElement;
    }

    /**
     * 添加图片元素
     *
     * @param imageStream 图片对象流
     * @param x     x坐标
     * @param y     y坐标
     * @return 图片元素
     */
    public ImageElement addImageElement(InputStream imageStream, int x, int y) throws IOException {
        return addImageElement(ImageIO.read(imageStream), x, y);
    }

    /**
     * 添加图片元素
     *
     * @param imgUrl   图片rul
     * @param x        x坐标
     * @param y        y坐标
     * @param width    宽度
     * @param height   高度
     * @param zoomMode 缩放模式
     * @return 图片元素
     */
    public ImageElement addImageElement(String imgUrl, int x, int y, int width, int height, ZoomMode zoomMode) {
        ImageElement imageElement = new ImageElement(imgUrl, x, y, width, height, zoomMode);
        this.combineElements.add(imageElement);
        return imageElement;
    }

    /**
     * 添加图片元素
     *
     * @param image    图片对象
     * @param x        x坐标
     * @param y        y坐标
     * @param width    宽度
     * @param height   高度
     * @param zoomMode 缩放模式
     * @return 图片元素
     */
    public ImageElement addImageElement(BufferedImage image, int x, int y, int width, int height, ZoomMode zoomMode) {
        ImageElement imageElement = new ImageElement(image, x, y, width, height, zoomMode);
        this.combineElements.add(imageElement);
        return imageElement;
    }

    /**
     * 添加图片元素
     *
     * @param imageStream    图片对象输入流
     * @param x        x坐标
     * @param y        y坐标
     * @param width    宽度
     * @param height   高度
     * @param zoomMode 缩放模式
     * @return 图片元素
     */
    public ImageElement addImageElement(InputStream imageStream, int x, int y, int width, int height, ZoomMode zoomMode) throws IOException {
        return addImageElement(ImageIO.read(imageStream), x, y, width, height, zoomMode);
    }

    /**
     * 添加文本元素
     *
     * @param text 文本
     * @param font Font对象
     * @param x    x坐标
     * @param y    y坐标
     * @return 文本元素
     */
    public TextElement addTextElement(String text, Font font, int x, int y) {
        TextElement textElement = new TextElement(text, font, x, y);
        this.combineElements.add(textElement);
        return textElement;
    }

    /**
     * 添加文本元素
     *
     * @param text     文本
     * @param fontSize 字体大小
     * @param x        x坐标
     * @param y        y坐标
     * @return 文本元素
     */
    public TextElement addTextElement(String text, int fontSize, int x, int y) {
        TextElement textElement = new TextElement(text, fontSize, x, y);
        this.combineElements.add(textElement);
        return textElement;
    }

    /**
     * 添加文本元素
     *
     * @param text     文本
     * @param fontName 字体名称
     * @param fontSize 字体大小
     * @param x        x坐标
     * @param y        y坐标
     * @return 文本元素
     */
    public TextElement addTextElement(String text, String fontName, int fontSize, int x, int y) {
        TextElement textElement = new TextElement(text, fontName, fontSize, x, y);
        this.combineElements.add(textElement);
        return textElement;
    }

    /**
     * 添加矩形元素
     *
     * @param x      x坐标
     * @param y      y坐标
     * @param width  宽度
     * @param height 高度
     * @return 矩形元素
     */
    public RectangleElement addRectangleElement(int x, int y, int width, int height) {
        RectangleElement rectangleElement = new RectangleElement(x, y, width, height);
        this.combineElements.add(rectangleElement);
        return rectangleElement;
    }

//-----------------------------------------------  添加水印  ----------------------------------------------------------//

    /**
     * 添加文本水印（指定位置）
     *
     * @param text 文本
     * @param font Font对象
     * @param x    x坐标
     * @param y    y坐标
     * @return 文本元素
     */
    public TextElement addWaterMark(String text, Font font, int x, int y) {
        return addTextElement(text, font, x, y);
    }

    /**
     * 添加文本水印（指定位置）
     *
     * @param text     文本
     * @param fontSize 字体大小
     * @param x        x坐标
     * @param y        y坐标
     * @return 文本元素
     */
    public TextElement addWaterMark(String text, int fontSize, int x, int y) {
        return addTextElement(text, fontSize, x, y);
    }

    /**
     * 添加文本水印（指定位置）
     *
     * @param text     文本
     * @param fontName 字体名称
     * @param fontSize 字体大小
     * @param x        x坐标
     * @param y        y坐标
     * @return 文本元素
     */
    public TextElement addWaterMark(String text, String fontName, int fontSize, int x, int y) {
        return addTextElement(text, fontName, fontSize, x, y);
    }

    /**
     * 添加文字水印（平铺模式）
     *
     * @apiNote 水印字体默认 宋体，加粗，30
     * @param text 水印文字
     */
    public void addWatermark(String text) {
        //水印字体，大小
        Font font = new Font("宋体", Font.BOLD, 30);
        addWatermark(text, font, null, -45, 0.6f);
    }

    /**
     * 添加文字水印
     *
     * @param text      水印文字
     * @param font      字体
     * @param color     文字颜色
     * @param rotate   水印旋转角度(应在正负45度之间)
     * @param alpha    水印透明度
     */
    public void addWatermark(String text, Font font, Color color, Integer rotate, Float alpha) {
        //水印颜色
        JLabel label = new JLabel(text);
        FontMetrics metrics = label.getFontMetrics(font);
        //文字水印的宽
        int width = metrics.stringWidth(label.getText());
        // 间隔宽度默认和文字的宽度保持一致
        int height = width;
        // 图片的高  除以  文字水印的高度  打印的行数(以文字水印的宽为间隔)
        int rows = this.canvasHeight / height;
        //图片的宽 除以 文字水印的宽  每行打印的列数(以文字水印的宽为间隔)
        int cols = (int) Math.ceil(this.canvasWidth / width);
        //防止图片太小而文字水印太长，所以至少打印一次
        rows = rows < 1 ? 1 : rows;
        cols = cols < 1 ? 1 : cols;
        TextElement textEle = null;
        for (int j = 0; j < rows; j++) {
            for (int i = 0; i < cols; i++) {
                textEle = new TextElement(text, font, 5 + i * width, j * width);
                textEle.setRotate(rotate);
                textEle.setAlpha(alpha);
                if (null != color) {
                    textEle.setColor(color);
                }
                this.combineElements.add(textEle);
            }
        }
    }

    /**
     * 添加图片水印
     *
     * @param imgUrl 图片url
     * @param x      x坐标
     * @param y      y坐标
     * @return 图片元素
     */
    public ImageElement addWaterMark(String imgUrl, int x, int y) throws Exception {
        return addImageElement(imgUrl, x, y);
    }

    /**
     * 添加图片水印
     *
     * @param image 图片对象
     * @param x     x坐标
     * @param y     y坐标
     * @return 图片元素
     */
    public ImageElement addWaterMark(BufferedImage image, int x, int y) {
        return addImageElement(image, x, y);
    }

    /**
     * 添加图片水印
     *
     * @param imageStream 图片对象流
     * @param x     x坐标
     * @param y     y坐标
     * @return 图片元素
     */
    public ImageElement addWaterMark(InputStream imageStream, int x, int y) throws IOException {
        return addImageElement(ImageIO.read(imageStream), x, y);
    }

    /**
     * 添加图片水印
     *
     * @param imgUrl   图片rul
     * @param x        x坐标
     * @param y        y坐标
     * @param width    宽度
     * @param height   高度
     * @param zoomMode 缩放模式
     * @return 图片元素
     */
    public ImageElement addWaterMark(String imgUrl, int x, int y, int width, int height, ZoomMode zoomMode) {
        return addImageElement(imgUrl, x, y, width, height, zoomMode);
    }

    /**
     * 添加图片水印
     *
     * @param image    图片对象
     * @param x        x坐标
     * @param y        y坐标
     * @param width    宽度
     * @param height   高度
     * @param zoomMode 缩放模式
     * @return 图片元素
     */
    public ImageElement addWaterMark(BufferedImage image, int x, int y, int width, int height, ZoomMode zoomMode) {
        return addImageElement(image, x, y, width, height, zoomMode);
    }

    /**
     * 添加图片水印
     *
     * @param imageStream    图片对象输入流
     * @param x        x坐标
     * @param y        y坐标
     * @param width    宽度
     * @param height   高度
     * @param zoomMode 缩放模式
     * @return 图片元素
     */
    public ImageElement addWaterMark(InputStream imageStream, int x, int y, int width, int height, ZoomMode zoomMode) throws IOException {
        return addImageElement(ImageIO.read(imageStream), x, y, width, height, zoomMode);
    }


    /**
     * 给图片添加水印
     *
     * @param markImg     水印图片
     * @param marginX      水印之间的水平间距
     * @param marginY      水印之间的垂直间距
     * @param opacity       水印透明度
     * @param rotate        水印旋转角度，应在正负45度之间
     * @throws IOException
     */
    public void addWaterMark(BufferedImage markImg, int marginX, int marginY, float opacity, Integer rotate)  {

        // 水印图片宽度
        int markWidth = markImg.getWidth();
        System.out.println("w:" + markWidth);
        // 水印图片高度
        int markHeight = markImg.getHeight();
        System.out.println("h:" + markHeight);
        int interval = markWidth + markHeight;
        // 图片的高  除以  文字水印的高度  打印的行数(以文字水印的宽为间隔)
        int rows = (int) Math.ceil(this.canvasHeight / (markHeight + marginY));
        System.out.println("rows:  " + rows);
        //图片的宽 除以 文字水印的宽  每行打印的列数(以文字水印的宽为间隔)
        int cols = (int) Math.ceil(this.canvasWidth / markWidth);
        System.out.println("cols:   " + cols);
        ImageElement imgEle = null;
        for (int j = 0; j < rows; j++) {
            for (int i = 0; i < cols; i++) {
                imgEle = new ImageElement(markImg, i * (markWidth + marginX), j * (markHeight + marginY));
                imgEle.setRotate(rotate);
                imgEle.setAlpha(opacity);
                this.combineElements.add(imgEle);
            }
        }
    }


    public static void main(String[] args) {
        System.out.println(Math.ceil(1080.0 / 245));
        System.out.println(1080.1 / 245);
    }
}
